-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSFProofFont.cs
More file actions
221 lines (196 loc) · 8.6 KB
/
SFProofFont.cs
File metadata and controls
221 lines (196 loc) · 8.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
using System;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace SpriteFontProofFont;
public sealed class SFProofFont : IDisposable {
private class AtlasData {
public Texture2D Atlas = null!;
public Dictionary<char, (int x, int w)> CharsData = [];
public int Size;
}
private readonly List<AtlasData> _atlases = [];
public float Spacing { get; set; } = 10;
public float LineSpacing { get; set; } = 5;
public SFProofFont(GraphicsDevice graphics, string sfpfFilePath) {
string tempFolder = Path.Combine(Path.GetTempPath(), $"SFPFTemp_{Guid.NewGuid()}");
try {
ZipFile.ExtractToDirectory(sfpfFilePath, tempFolder);
string metadataPath = Path.Combine(tempFolder, "metadata");
if (!File.Exists(metadataPath)) {
throw new Exception("Font file is missing metadata. Please regenerate with new generator.");
}
string metadata = File.ReadAllText(metadataPath);
var sizes = metadata.Replace("sizes:", "").Trim()
.Split(',')
.Select(s => int.Parse(s.Trim()))
.OrderBy(s => s);
foreach (int size in sizes) {
string atlasPath = Path.Combine(tempFolder, $"atlas_{size}");
string charsDataPath = Path.Combine(tempFolder, $"chars_data_{size}");
if (!File.Exists(atlasPath) || !File.Exists(charsDataPath)) {
continue;
}
var atlasData = new AtlasData {
CharsData = []
};
using (var stream = File.OpenRead(atlasPath)) {
atlasData.Atlas = Texture2D.FromStream(graphics, stream);
}
Color[] buffer = new Color[atlasData.Atlas.Width * atlasData.Atlas.Height];
atlasData.Atlas.GetData(buffer);
for (int i = 0; i < buffer.Length; i++) {
buffer[i] = Color.FromNonPremultiplied(
buffer[i].R, buffer[i].G, buffer[i].B, buffer[i].A
);
}
atlasData.Atlas.SetData(buffer);
var charIntPairs = File.ReadAllText(charsDataPath).Split('\n');
foreach (var cip in charIntPairs) {
if (string.IsNullOrWhiteSpace(cip)) continue;
var inTChar = cip.Split(' ');
char c = inTChar[0][0];
int x = int.Parse(inTChar[1]);
int w = int.Parse(inTChar[2]);
atlasData.CharsData.Add(c, (x, w));
}
atlasData.Size = atlasData.Atlas.Height;
_atlases.Add(atlasData);
}
if (_atlases.Count == 0) {
throw new Exception("No valid atlases found in font file");
}
}
finally {
if (Directory.Exists(tempFolder)) {
Directory.Delete(tempFolder, true);
}
}
}
private AtlasData GetBestAtlas(float targetHeight) {
int left = 0;
int right = _atlases.Count - 1;
if (targetHeight <= _atlases[0].Size) return _atlases[0];
if (targetHeight >= _atlases[right].Size) return _atlases[right];
while (left <= right) {
int mid = left + (right - left) / 2;
int atlasSize = _atlases[mid].Size;
if (atlasSize == targetHeight) {
return _atlases[mid];
}
if (atlasSize < targetHeight) {
left = mid + 1;
}
else {
right = mid - 1;
}
}
if (left >= _atlases.Count) return _atlases[right];
if (right < 0) return _atlases[left];
float distLeft = Math.Abs(_atlases[left].Size - targetHeight);
float distRight = Math.Abs(_atlases[right].Size - targetHeight);
return distLeft <= distRight ? _atlases[left] : _atlases[right];
}
public Vector2 MeasureString(string text, float height, float? spacing = null, float? lineSpacing = null, int? index = null, int? length = null) {
if (text.Length == 0) return Vector2.Zero;
var largestAtlas = GetBestAtlas(int.MaxValue);
float spacingScale = height / 120f;
float lAtlasScale = height / largestAtlas.Atlas.Height;
Vector2 size = Vector2.UnitY * height;
float currWidth = 0;
spacing ??= Spacing;
lineSpacing ??= LineSpacing;
index ??= 0;
length ??= text.Length;
for (int i = index.Value; i < index.Value + length.Value && i < text.Length; i++) {
char c = text[i];
if (c == ' ') {
if (largestAtlas.CharsData.TryGetValue('$', out var dlrData)) {
currWidth += dlrData.w * lAtlasScale - spacing.Value * 2 * spacingScale;
}
else {
currWidth += spacing.Value * spacingScale;
}
continue;
}
else if (c == '\n') {
size.Y += height + lineSpacing.Value * spacingScale;
size.X = Math.Max(size.X, currWidth - spacing.Value * spacingScale);
currWidth = 0;
continue;
}
if (!largestAtlas.CharsData.TryGetValue(c, out var offset)) {
if (!largestAtlas.CharsData.TryGetValue('?', out var fallback))
continue;
offset = fallback;
}
currWidth += offset.w * lAtlasScale + spacing.Value * spacingScale;
}
size.X = Math.Max(size.X, currWidth - spacing.Value * spacingScale);
return size;
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float height, float rotation, float? spacing = null, float? lineSpacing = null, int? index = null, int? length = null) {
if (text.Length == 0) return;
var atlas = GetBestAtlas(height);
var atlas2 = GetBestAtlas(int.MaxValue);
float spacingScale = height / 120f;
float atlasScale = height / atlas.Size;
float atlas2Scale = height / atlas2.Size;
float curr_x = position.X;
float curr_y = position.Y;
spacing ??= Spacing;
lineSpacing ??= LineSpacing;
index ??= 0;
length ??= text.Length;
for (int i = index.Value; i < index.Value + length.Value && i < text.Length; i++) {
char c = text[i];
if (c == ' ') {
if (atlas2.CharsData.TryGetValue('$', out var dlrData)) {
curr_x += dlrData.w * atlas2Scale - spacing.Value * 2 * spacingScale;
}
else {
curr_x += spacing.Value * spacingScale;
}
continue;
}
else if (c == '\n') {
curr_x = position.X;
curr_y += height + lineSpacing.Value * spacingScale;
continue;
}
if (!atlas.CharsData.TryGetValue(c, out var offset)) {
if (!atlas.CharsData.TryGetValue('?', out offset))
continue;
}
Rectangle sourceRect = new(offset.x, 0, offset.w, atlas.Size);
Vector2 drawPos = new(curr_x, curr_y);
if (rotation != 0) {
drawPos.RotateAround(position, rotation);
}
sb.Draw(atlas.Atlas, drawPos, sourceRect, color, rotation, Vector2.Zero, atlasScale, SpriteEffects.None, 0);
if (!atlas2.CharsData.TryGetValue(c, out var offset2)) {
if (!atlas2.CharsData.TryGetValue('?', out offset2))
continue;
}
curr_x += offset2.w * atlas2Scale + spacing.Value * spacingScale;
}
}
public void DrawStringOutlined(SpriteBatch sb, string text, Vector2 position, Color color, Color outlineColor, float height, float outlineWidth, float step, float rotation, float? spacing = null, float? lineSpacing = null) {
for (float i = -1; i <= 1; i += step) {
for (float j = -1; j <= 1; j += step) {
Vector2 offset = new Vector2(i, j) * outlineWidth;
DrawString(sb, text, position + offset, outlineColor, height, rotation, spacing, lineSpacing);
}
}
DrawString(sb, text, position, color, height, 0, spacing, lineSpacing);
}
public void Dispose() {
foreach (var atlas in _atlases) {
atlas.Atlas?.Dispose();
}
_atlases.Clear();
}
}