1
1
// / AngelCode BMFont parser & generator
2
2
// / Copyright: Public Domain
3
3
// / Author: Jan Jurzitza
4
- module bmfont ;
4
+ module bmfont ; @safe :
5
5
6
- import std.traits : isSomeString ;
7
- import std.ascii : isWhite, isAlpha ;
6
+ import std.array : Appender, appender ;
7
+ import std.ascii : isAlpha, isWhite ;
8
8
import std.bitmanip : littleEndianToNative, nativeToLittleEndian;
9
- import std.string : splitLines, stripLeft, strip;
10
9
import std.conv : to;
10
+ import std.string : representation, splitLines, strip, stripLeft;
11
+ import std.traits : isSomeString;
11
12
12
13
// / Information what each channel contains
13
14
enum ChannelType : ubyte
@@ -168,7 +169,7 @@ struct Font
168
169
FontType type = FontType.none;
169
170
170
171
// / Creates a text representation of this font
171
- string toString () const @safe
172
+ string toString () const @safe pure
172
173
{
173
174
import std.format ;
174
175
@@ -208,48 +209,78 @@ struct Font
208
209
}
209
210
210
211
// / Creates a binary representation of this font
211
- ubyte [] toBinary () const
212
+ ubyte [] toBinary () const @safe pure
212
213
{
213
214
import std.string : toStringz;
214
215
215
- ubyte [] header = cast (ubyte [])[66 , 77 , 70 , 3 ]; // BMF v3
216
- ubyte [] binfo, bcommon, bpages, bchars, bkernings;
217
-
218
- binfo = nativeToLittleEndian(info.fontSize) ~ info.bitField
219
- ~ info.charSet ~ nativeToLittleEndian(
220
- info.stretchH) ~ info.aa ~ info.padding ~ info.spacing ~ info.outline;
221
- binfo ~= cast (ubyte []) info.fontName ~ 0u ;
222
-
223
- bcommon = nativeToLittleEndian(common.lineHeight) ~ nativeToLittleEndian(common.base) ~ nativeToLittleEndian(
224
- common.scaleW) ~ nativeToLittleEndian(common.scaleH) ~ nativeToLittleEndian(
225
- common.pages) ~ common.bitField ~ common.alphaChnl ~ common.redChnl
226
- ~ common.greenChnl ~ common.blueChnl;
216
+ auto header = appender(cast (ubyte [])[66 , 77 , 70 , 3 ]); // BMF v3
217
+ auto binfo = appender! (ubyte []);
218
+ auto bcommon = appender! (ubyte []);
219
+ auto bpages = appender! (ubyte []);
220
+ auto bchars = appender! (ubyte []);
221
+ auto bkernings = appender! (ubyte []);
222
+
223
+ binfo.putRange(nativeToLittleEndian(info.fontSize));
224
+ binfo.put(info.bitField);
225
+ binfo.put(info.charSet);
226
+ binfo.putRange(nativeToLittleEndian(info.stretchH));
227
+ binfo.put(info.aa);
228
+ binfo.putRange(info.padding);
229
+ binfo.putRange(info.spacing);
230
+ binfo.put(info.outline);
231
+ binfo.putRange(info.fontName);
232
+ binfo.put(cast (ubyte ) 0u );
233
+
234
+ bcommon.putRange(nativeToLittleEndian(common.lineHeight));
235
+ bcommon.putRange(nativeToLittleEndian(common.base));
236
+ bcommon.putRange(nativeToLittleEndian(common.scaleW));
237
+ bcommon.putRange(nativeToLittleEndian(common.scaleH));
238
+ bcommon.putRange(nativeToLittleEndian(common.pages));
239
+ bcommon.put(common.bitField);
240
+ bcommon.put(common.alphaChnl);
241
+ bcommon.put(common.redChnl);
242
+ bcommon.put(common.greenChnl);
243
+ bcommon.put(common.blueChnl);
227
244
228
245
foreach (page; pages)
229
- bpages ~= cast (ubyte []) page ~ 0u ;
246
+ {
247
+ bpages.putRange(page);
248
+ bpages.put(cast (ubyte ) 0u );
249
+ }
230
250
231
251
foreach (c; chars)
232
- bchars ~= nativeToLittleEndian(cast (uint ) c.id) ~ nativeToLittleEndian(c.x) ~ nativeToLittleEndian(
233
- c.y) ~ nativeToLittleEndian(c.width) ~ nativeToLittleEndian(c.height) ~ nativeToLittleEndian(
234
- c.xoffset) ~ nativeToLittleEndian(c.yoffset) ~ nativeToLittleEndian(c.xadvance) ~ c.page
235
- ~ c.chnl;
252
+ {
253
+ bchars.putRange(nativeToLittleEndian(cast (uint ) c.id));
254
+ bchars.putRange(nativeToLittleEndian(c.x));
255
+ bchars.putRange(nativeToLittleEndian(c.y));
256
+ bchars.putRange(nativeToLittleEndian(c.width));
257
+ bchars.putRange(nativeToLittleEndian(c.height));
258
+ bchars.putRange(nativeToLittleEndian(c.xoffset));
259
+ bchars.putRange(nativeToLittleEndian(c.yoffset));
260
+ bchars.putRange(nativeToLittleEndian(c.xadvance));
261
+ bchars.put(c.page);
262
+ bchars.put(c.chnl);
263
+ }
236
264
237
265
foreach (k; kernings)
238
- bkernings ~= nativeToLittleEndian(cast (uint ) k.first) ~ nativeToLittleEndian(
239
- cast (uint ) k.second) ~ nativeToLittleEndian(k.amount);
266
+ {
267
+ bkernings.putRange(nativeToLittleEndian(cast (uint ) k.first));
268
+ bkernings.putRange(nativeToLittleEndian(cast (uint ) k.second));
269
+ bkernings.putRange(nativeToLittleEndian(k.amount));
270
+ }
240
271
241
- // dfmt off
242
- return header
243
- ~ 1u ~ nativeToLittleEndian (cast (uint ) binfo.length) ~ binfo
244
- ~ 2u ~ nativeToLittleEndian(cast (uint ) bcommon. length) ~ bcommon
245
- ~ 3u ~ nativeToLittleEndian( cast ( uint ) bpages.length) ~ bpages
246
- ~ 4u ~ nativeToLittleEndian( cast ( uint ) bchars.length) ~ bchars
247
- ~ 5u ~ nativeToLittleEndian( cast ( uint ) bkernings.length) ~ bkernings;
248
- // dfmt on
272
+ foreach (i, block; [binfo, bcommon, bpages, bchars, bkernings])
273
+ {
274
+ header.put (cast (ubyte )(i + 1 ));
275
+ header.putRange( nativeToLittleEndian(cast (uint ) block.data. length));
276
+ header.put(block.data);
277
+ }
278
+
279
+ return header.data;
249
280
}
250
281
251
282
// / Returns: Information about the character passed as argument `c`. Empty Char struct with dchar.init as id if not found.
252
- Char getChar (dchar id) nothrow
283
+ Char getChar (dchar id) const nothrow pure
253
284
{
254
285
foreach (c; chars)
255
286
if (c.id == id)
@@ -258,7 +289,7 @@ struct Font
258
289
}
259
290
260
291
// / Returns: the kerning between two characters. This is the additional distance the `second` character should be moved if the character before that is `first`
261
- short getKerning (dchar first, dchar second)
292
+ short getKerning (dchar first, dchar second) const nothrow pure
262
293
{
263
294
foreach (kerning; kernings)
264
295
if (kerning.first == first && kerning.second == second)
@@ -267,13 +298,31 @@ struct Font
267
298
}
268
299
}
269
300
301
+ private void putRange (size_t n)(ref Appender! (ubyte []) dst, ubyte [n] src) pure
302
+ {
303
+ foreach (b; src)
304
+ dst.put(b);
305
+ }
306
+
307
+ private void putRange (ref Appender! (ubyte []) dst, scope const (char )[] src) pure
308
+ {
309
+ foreach (char c; src)
310
+ dst.put(cast (ubyte ) c);
311
+ }
312
+
270
313
private string escape (in string s) pure nothrow @safe
271
314
{
272
315
import std.string : replace;
273
316
274
317
return s.replace(" \\ " , " \\\\ " ).replace(" \" " , " \\\" " );
275
318
}
276
319
320
+ private const (ubyte )[] getReadonlyBytes (T)(return scope T[] data) pure
321
+ if (T.sizeof == 1 )
322
+ {
323
+ return (() @trusted => cast (const (ubyte )[]) data)();
324
+ }
325
+
277
326
// / Gets thrown if a Font is in an invalid format
278
327
class InvalidFormatException : Exception
279
328
{
@@ -284,8 +333,8 @@ class InvalidFormatException : Exception
284
333
}
285
334
286
335
// / Parses a font and automatically figures out if its binary or text. Pass additional ParseFlags to skip sections.
287
- Font parseFnt (T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if (
288
- isSomeString! T || is (T == ubyte []))
336
+ Font parseFnt (T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure
337
+ if ( isSomeString! T || is (T == ubyte []))
289
338
{
290
339
Font font;
291
340
@@ -296,15 +345,15 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
296
345
bool parseFontName = false ;
297
346
size_t curPage = 0 ;
298
347
uint pageTotal = 0 ;
299
- if (cast ( ubyte []) data[0 .. 3 ] == cast ( ubyte []) " BMF" )
348
+ if (data[0 .. 3 ].getReadonlyBytes == " BMF" .representation )
300
349
{
301
350
font.type = FontType.binary;
302
351
font.fileVersion = data[3 ];
303
352
if (font.fileVersion != 3 )
304
353
throw new InvalidFormatException(
305
- " Font version is not supported: " ~ font.fileVersion.to! string );
354
+ " Font version is not supported: " ~ font.fileVersion.to! string );
306
355
}
307
- else if (cast ( ubyte []) data[0 .. 4 ] == cast ( ubyte []) " <?xm" )
356
+ else if (data[0 .. 4 ].getReadonlyBytes == " <?xm" .representation )
308
357
font.type = FontType.xml;
309
358
else
310
359
font.type = FontType.text;
@@ -471,7 +520,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
471
520
}
472
521
break ;
473
522
case FontType.text:
474
- foreach (line; (cast (string ) data).splitLines)
523
+ foreach (line; (cast (const ( char )[] ) data).splitLines)
475
524
{
476
525
string type;
477
526
foreach (c; line)
@@ -481,7 +530,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
481
530
type ~= c;
482
531
}
483
532
line = line[type.length .. $].stripLeft;
484
- string [2 ][] arguments = line.getArguments();
533
+ const ( char )[] [2 ][] arguments = line.getArguments();
485
534
ushort pageID = 0 ;
486
535
switch (type)
487
536
{
@@ -493,7 +542,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
493
542
switch (argument[0 ])
494
543
{
495
544
case " face" :
496
- font.info.fontName = argument[1 ];
545
+ font.info.fontName = argument[1 ].idup ;
497
546
break ;
498
547
case " size" :
499
548
font.info.fontSize = argument[1 ].to! short ;
@@ -529,7 +578,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
529
578
font.info.outline = cast (ubyte ) argument[1 ].to! uint ;
530
579
break ;
531
580
default :
532
- throw new InvalidFormatException(" Unkown info argument: " ~ argument[0 ]);
581
+ throw new InvalidFormatException(" Unkown info argument: " ~ argument[0 ].to ! string );
533
582
}
534
583
}
535
584
break ;
@@ -572,7 +621,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
572
621
font.common.blueChnl = cast (ChannelType) argument[1 ].to! uint ;
573
622
break ;
574
623
default :
575
- throw new InvalidFormatException(" Unkown common argument: " ~ argument[0 ]);
624
+ throw new InvalidFormatException(" Unkown common argument: " ~ argument[0 ].to ! string );
576
625
}
577
626
}
578
627
break ;
@@ -587,10 +636,10 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
587
636
pageID = argument[1 ].to! ushort ;
588
637
break ;
589
638
case " file" :
590
- font.pages[pageID] = argument[1 ];
639
+ font.pages[pageID] = argument[1 ].idup ;
591
640
break ;
592
641
default :
593
- throw new InvalidFormatException(" Unkown page argument: " ~ argument[0 ]);
642
+ throw new InvalidFormatException(" Unkown page argument: " ~ argument[0 ].to ! string );
594
643
}
595
644
}
596
645
break ;
@@ -667,7 +716,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
667
716
kerning.amount = argument[1 ].to! short ;
668
717
break ;
669
718
default :
670
- throw new InvalidFormatException(" Unkown kerning argument: " ~ argument[0 ]);
719
+ throw new InvalidFormatException(" Unkown kerning argument: " ~ argument[0 ].to ! string );
671
720
}
672
721
}
673
722
font.kernings ~= kerning;
@@ -686,10 +735,10 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
686
735
return font;
687
736
}
688
737
689
- private string [ 2 ][] getArguments (ref in string line) pure @safe
738
+ private const ( char )[][ 2 ][] getArguments (ref scope const ( char )[] line) pure @safe
690
739
{
691
- string [2 ][] args;
692
- string [2 ] currArg = " " ;
740
+ const ( char )[] [2 ][] args;
741
+ const ( char )[] [2 ] currArg = " " ;
693
742
bool inString = false ;
694
743
bool escape = false ;
695
744
bool isKey = true ;
@@ -825,7 +874,7 @@ kerning first=97 second=98 amount=-1
825
874
assert (font.kernings[0 ].amount == - 1 );
826
875
}
827
876
828
- unittest
877
+ @system unittest
829
878
{
830
879
import std.file ;
831
880
@@ -838,7 +887,7 @@ unittest
838
887
assert (binary.type != text.type);
839
888
}
840
889
841
- unittest
890
+ @system unittest
842
891
{
843
892
import std.file ;
844
893
@@ -851,7 +900,7 @@ unittest
851
900
assert (binary.type != text.type);
852
901
}
853
902
854
- unittest
903
+ @system unittest
855
904
{
856
905
import std.file ;
857
906
0 commit comments