Skip to content

Commit 8aac933

Browse files
committed
make library @safe and pure
1 parent 4f88f23 commit 8aac933

File tree

1 file changed

+103
-54
lines changed

1 file changed

+103
-54
lines changed

source/bmfont.d

Lines changed: 103 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/// AngelCode BMFont parser & generator
22
/// Copyright: Public Domain
33
/// Author: Jan Jurzitza
4-
module bmfont;
4+
module bmfont; @safe:
55

6-
import std.traits : isSomeString;
7-
import std.ascii : isWhite, isAlpha;
6+
import std.array : Appender, appender;
7+
import std.ascii : isAlpha, isWhite;
88
import std.bitmanip : littleEndianToNative, nativeToLittleEndian;
9-
import std.string : splitLines, stripLeft, strip;
109
import std.conv : to;
10+
import std.string : representation, splitLines, strip, stripLeft;
11+
import std.traits : isSomeString;
1112

1213
/// Information what each channel contains
1314
enum ChannelType : ubyte
@@ -168,7 +169,7 @@ struct Font
168169
FontType type = FontType.none;
169170

170171
/// Creates a text representation of this font
171-
string toString() const @safe
172+
string toString() const @safe pure
172173
{
173174
import std.format;
174175

@@ -208,48 +209,78 @@ struct Font
208209
}
209210

210211
/// Creates a binary representation of this font
211-
ubyte[] toBinary() const
212+
ubyte[] toBinary() const @safe pure
212213
{
213214
import std.string : toStringz;
214215

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);
227244

228245
foreach (page; pages)
229-
bpages ~= cast(ubyte[]) page ~ 0u;
246+
{
247+
bpages.putRange(page);
248+
bpages.put(cast(ubyte) 0u);
249+
}
230250

231251
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+
}
236264

237265
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+
}
240271

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;
249280
}
250281

251282
/// 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
253284
{
254285
foreach (c; chars)
255286
if (c.id == id)
@@ -258,7 +289,7 @@ struct Font
258289
}
259290

260291
/// 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
262293
{
263294
foreach (kerning; kernings)
264295
if (kerning.first == first && kerning.second == second)
@@ -267,13 +298,31 @@ struct Font
267298
}
268299
}
269300

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+
270313
private string escape(in string s) pure nothrow @safe
271314
{
272315
import std.string : replace;
273316

274317
return s.replace("\\", "\\\\").replace("\"", "\\\"");
275318
}
276319

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+
277326
/// Gets thrown if a Font is in an invalid format
278327
class InvalidFormatException : Exception
279328
{
@@ -284,8 +333,8 @@ class InvalidFormatException : Exception
284333
}
285334

286335
/// 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[]))
289338
{
290339
Font font;
291340

@@ -296,15 +345,15 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
296345
bool parseFontName = false;
297346
size_t curPage = 0;
298347
uint pageTotal = 0;
299-
if (cast(ubyte[]) data[0 .. 3] == cast(ubyte[]) "BMF")
348+
if (data[0 .. 3].getReadonlyBytes == "BMF".representation)
300349
{
301350
font.type = FontType.binary;
302351
font.fileVersion = data[3];
303352
if (font.fileVersion != 3)
304353
throw new InvalidFormatException(
305-
"Font version is not supported: " ~ font.fileVersion.to!string);
354+
"Font version is not supported: " ~ font.fileVersion.to!string);
306355
}
307-
else if (cast(ubyte[]) data[0 .. 4] == cast(ubyte[]) "<?xm")
356+
else if (data[0 .. 4].getReadonlyBytes == "<?xm".representation)
308357
font.type = FontType.xml;
309358
else
310359
font.type = FontType.text;
@@ -471,7 +520,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
471520
}
472521
break;
473522
case FontType.text:
474-
foreach (line; (cast(string) data).splitLines)
523+
foreach (line; (cast(const(char)[]) data).splitLines)
475524
{
476525
string type;
477526
foreach (c; line)
@@ -481,7 +530,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
481530
type ~= c;
482531
}
483532
line = line[type.length .. $].stripLeft;
484-
string[2][] arguments = line.getArguments();
533+
const(char)[][2][] arguments = line.getArguments();
485534
ushort pageID = 0;
486535
switch (type)
487536
{
@@ -493,7 +542,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
493542
switch (argument[0])
494543
{
495544
case "face":
496-
font.info.fontName = argument[1];
545+
font.info.fontName = argument[1].idup;
497546
break;
498547
case "size":
499548
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
529578
font.info.outline = cast(ubyte) argument[1].to!uint;
530579
break;
531580
default:
532-
throw new InvalidFormatException("Unkown info argument: " ~ argument[0]);
581+
throw new InvalidFormatException("Unkown info argument: " ~ argument[0].to!string);
533582
}
534583
}
535584
break;
@@ -572,7 +621,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
572621
font.common.blueChnl = cast(ChannelType) argument[1].to!uint;
573622
break;
574623
default:
575-
throw new InvalidFormatException("Unkown common argument: " ~ argument[0]);
624+
throw new InvalidFormatException("Unkown common argument: " ~ argument[0].to!string);
576625
}
577626
}
578627
break;
@@ -587,10 +636,10 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
587636
pageID = argument[1].to!ushort;
588637
break;
589638
case "file":
590-
font.pages[pageID] = argument[1];
639+
font.pages[pageID] = argument[1].idup;
591640
break;
592641
default:
593-
throw new InvalidFormatException("Unkown page argument: " ~ argument[0]);
642+
throw new InvalidFormatException("Unkown page argument: " ~ argument[0].to!string);
594643
}
595644
}
596645
break;
@@ -667,7 +716,7 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
667716
kerning.amount = argument[1].to!short;
668717
break;
669718
default:
670-
throw new InvalidFormatException("Unkown kerning argument: " ~ argument[0]);
719+
throw new InvalidFormatException("Unkown kerning argument: " ~ argument[0].to!string);
671720
}
672721
}
673722
font.kernings ~= kerning;
@@ -686,10 +735,10 @@ Font parseFnt(T)(auto ref in T data, ParseFlags flags = ParseFlags.none) pure if
686735
return font;
687736
}
688737

689-
private string[2][] getArguments(ref in string line) pure @safe
738+
private const(char)[][2][] getArguments(ref scope const(char)[] line) pure @safe
690739
{
691-
string[2][] args;
692-
string[2] currArg = "";
740+
const(char)[][2][] args;
741+
const(char)[][2] currArg = "";
693742
bool inString = false;
694743
bool escape = false;
695744
bool isKey = true;
@@ -825,7 +874,7 @@ kerning first=97 second=98 amount=-1
825874
assert(font.kernings[0].amount == -1);
826875
}
827876

828-
unittest
877+
@system unittest
829878
{
830879
import std.file;
831880

@@ -838,7 +887,7 @@ unittest
838887
assert(binary.type != text.type);
839888
}
840889

841-
unittest
890+
@system unittest
842891
{
843892
import std.file;
844893

@@ -851,7 +900,7 @@ unittest
851900
assert(binary.type != text.type);
852901
}
853902

854-
unittest
903+
@system unittest
855904
{
856905
import std.file;
857906

0 commit comments

Comments
 (0)